package com.tau.egonetwork.excel;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.tau.egonetwork.exceptions.ExcelCastException;
import com.tau.egonetwork.profiles.FriendsList;
import com.tau.egonetwork.profiles.MainProfile;
import com.tau.egonetwork.profiles.Profile;

public class ExcelWriteClusterUtils {

	private static final String CLUSTER_COLUMN_NAME = "Name";
	private static final String CLUSTER_COLUMN_ID = "Cluster #";

	private static final String SHEET_MATRICES_ROOT = "Cluster Matrices - With Root";
	private static final String SHEET_MAPPING_ROOT = "Cluster Mapping - With Root";
	private static final String SHEET_MATRICES_NO_ROOT = "Cluster Matrices - Without Root";
	private static final String SHEET_MAPPING_NO_ROOT = "Cluster Mapping - Without Root";

	public static <T extends Comparable<T>> void saveClusters(String filename,
			MainProfile<T> profile, Map<T, Integer> clustersWithRoot,
			Map<T, Integer> clustersWithoutRoot) {
		
		try {

			Workbook w;
			if (filename.endsWith(".xlsx")) {
				w = new XSSFWorkbook();
			} else if (filename.endsWith(".xls")) {
				w  = new HSSFWorkbook();
			} else {
				throw new ExcelCastException("Can only create either .xls or .xlsx files.");
			}
			
			String[] sheetMatricesNames = { SHEET_MATRICES_ROOT, SHEET_MATRICES_NO_ROOT };
			String[] sheetMappingsNames = { SHEET_MAPPING_ROOT, SHEET_MAPPING_NO_ROOT };
			ArrayList<Map<T, Integer>> sheetClustering = new ArrayList<Map<T,Integer>>();
			sheetClustering.add(clustersWithRoot);
			sheetClustering.add(clustersWithoutRoot);

			for (int i = 0; i < sheetMatricesNames.length; i++) {
				createMatrixSheet(w, profile, sheetClustering.get(i), sheetMatricesNames[i]);
				createNamesSheet(w, profile, sheetClustering.get(i), sheetMappingsNames[i]);				
			}
		
			FileOutputStream out = new FileOutputStream(new File(filename));
			w.write(out);
			out.close();
			
		} catch (IOException e) {
			throw new ExcelCastException("Could not create file " + filename + ".", e);
		} catch (Exception e) {
			throw new ExcelCastException("Error occurred while trying to write statistics to excel file.", e);
		}
		
	}
	
	private static <T extends Comparable<T>> void createMatrixSheet(Workbook w,
			MainProfile<T> profile, Map<T, Integer> clusters, String sheetName) {
		
		Sheet sheet = w.createSheet(sheetName);

		if (clusters == null || clusters.isEmpty() || profile.getFriendsList() == null)
			return;
		
		//find cluster with highest id value: (meaning, if there are 3 clusters, find value 3):
		int max = 0;
		for (int cluster : clusters.values()) {
			if (cluster > max)
				max = cluster;
		}
		
		ArrayList<ArrayList<Profile<T>>> profilesInClusters = new ArrayList<ArrayList<Profile<T>>>(max);
		//init each one of the arrayLists:
		for (int i  = 0; i < max; i++) {
			profilesInClusters.add(new ArrayList<Profile<T>>());
		}
		//go over the entire map and put profiles in their respective list:
		for (T t : clusters.keySet()) {
			int cluster = clusters.get(t);
			Profile<T> pt = profile.getFriendsList().get(t);
			if (pt == null) {
				//ID not in friendslist if its' the id of the egocenteric profile itself:
				pt = profile;
			}
			profilesInClusters.get(cluster-1).add(pt);
			
		}
		
		int rowNum = 0;
		int maxColumn = 0;
		int clusterCount = 0;
		
		for (ArrayList<Profile<T>> profilesInCluster : profilesInClusters) {
			
			if (rowNum > 0)  {
				//add two empty rows separating matrices apart from one another:
				sheet.createRow(rowNum++);
				sheet.createRow(rowNum++);
			}
			
			//create matrix representing this cluster:
			
			int numOfProfiles = profilesInCluster.size();
			int[][] mat = new int[numOfProfiles][numOfProfiles];
			
			for (int i = 0; i < numOfProfiles; i++) {
				
				Profile<T> p = profilesInCluster.get(i);
				FriendsList<T> mutualFriends = p.getFriendsList();
				if (mutualFriends == null)
					continue;
				
				//build a list of mutual friends, only from the real mutual friends who are
				//also in this cluster:
				FriendsList<T> mutualFriendsInCluster = new FriendsList<T>();				
				int mutual_size = mutualFriends.size();
				for (int j = 0; j < mutual_size; j++) {
					Profile<T> mutual = mutualFriends.get(j);
					if (profilesInCluster.contains(mutual))
						mutualFriendsInCluster.add(mutual);
				}
				
				int mutual_in_cluster_size = mutualFriendsInCluster.size();
				for (int j = 0; j < mutual_in_cluster_size; j++) {
					
					Profile<T> mutual = mutualFriendsInCluster.get(j);
					int globalIndexOfJ = findProfile(profilesInCluster, mutual.getID());
					
					mat[i][globalIndexOfJ] = 1;
				}
				
			}

			//create first row for this matrix with names of profiles in the clusters:
			Row firstRow = sheet.createRow(rowNum++);
			
			//create first cell and put profile name in it:
			int cellNum = 0;
			firstRow.createCell(cellNum++).setCellValue("Cluster #" + ++clusterCount);
			
			CellStyle cellStyle = w.createCellStyle();
			cellStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.index);
			cellStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
			cellStyle.setBorderBottom(CellStyle.BORDER_THIN);
			cellStyle.setBorderLeft(CellStyle.BORDER_THIN);
			cellStyle.setBorderRight(CellStyle.BORDER_THIN);
			cellStyle.setBorderTop(CellStyle.BORDER_THIN);

			//put profiles' names in header:
			for (int i = 0; i < numOfProfiles; i++) {
				Cell cell = firstRow.createCell(cellNum++);
				cell.setCellValue(profilesInCluster.get(i).getName());
				cell.setCellStyle(cellStyle);
			}
			
			//now for each profile create row and fill matrix data:
			for (int i = 0; i < numOfProfiles; i++) {
				
				Row row = sheet.createRow(rowNum++);
				cellNum = 0;
				
				//create first column cell:
				Cell cell = row.createCell(cellNum++);
				cell.setCellValue(profilesInCluster.get(i).getName());
				cell.setCellStyle(cellStyle);
							
				for (int j = 0; j < numOfProfiles; j++) {
					row.createCell(cellNum++).setCellValue(mat[i][j]);
				}
			}
			
			if (cellNum > maxColumn)
				maxColumn = cellNum;
			
		}

		for (int i = 0; i <= maxColumn; i++) {
			sheet.autoSizeColumn(i);
		}
		
	}
	
	private static <T extends Comparable<T>> void createNamesSheet(Workbook w,
			MainProfile<T> profile, Map<T, Integer> clusters, String sheetName) {
		
		Sheet sheet = w.createSheet(sheetName);

		//create headers:
		int rowNum = 0;
		Row header = sheet.createRow(rowNum++);
		
		int cellNum = 0;

		CellStyle headerCellStyle = w.createCellStyle();
		headerCellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.index);
		headerCellStyle.setFillPattern(CellStyle.SOLID_FOREGROUND);
		headerCellStyle.setBorderBottom(CellStyle.BORDER_THIN);
		headerCellStyle.setBorderTop(CellStyle.BORDER_THIN);
		headerCellStyle.setBorderLeft(CellStyle.BORDER_THIN);
		headerCellStyle.setBorderRight(CellStyle.BORDER_THIN);
		headerCellStyle.setAlignment(CellStyle.ALIGN_CENTER);
		Font headerFont = w.createFont();
		headerFont.setBoldweight(Font.BOLDWEIGHT_BOLD);
		headerCellStyle.setFont(headerFont);
		
		int numOfColumns = 2;
		header.createCell(cellNum++).setCellValue(CLUSTER_COLUMN_NAME);
		header.createCell(cellNum++).setCellValue(CLUSTER_COLUMN_ID);
		
		for (int i = 0; i < numOfColumns; i++) {
			header.getCell(i).setCellStyle(headerCellStyle);
		}
		
		if (clusters == null || clusters.isEmpty() || profile.getFriendsList() == null)
			return;

		//add egocenteric:
		Row egoRow = sheet.createRow(rowNum++);
		cellNum = 0;
		egoRow.createCell(cellNum++).setCellValue(profile.getName());
		if (clusters.containsKey(profile.getID()))
			egoRow.createCell(cellNum++).setCellValue(clusters.get(profile.getID()));
		
		for (Profile<T> p : profile.getFriendsList()) {
			Row row = sheet.createRow(rowNum++);
			cellNum = 0;
			row.createCell(cellNum++).setCellValue(p.getName());
			if (clusters.containsKey(p.getID()))
				row.createCell(cellNum++).setCellValue(clusters.get(p.getID()));			
		}
		
		sheet.createFreezePane(1, 1);
		for (int i = 0; i < numOfColumns; i++) {
			sheet.autoSizeColumn(i);
		}
		
	}
	
	private static <T extends Comparable<T>> int findProfile(ArrayList<Profile<T>> friends, T idToFind) {
		int size = friends.size();
		for (int i = 0; i < size; i++) {
			if (friends.get(i).getID().equals(idToFind))
					return i;
				
		}
		return -1;
	}

}
